///PHILOTES Source Code.  (C)2012 PhiloLabs
#pragma once

#include "ExtrasPrerequisites.h "

#include "OgreMovableObject.h"
#include "OgreRenderable.h"
#include "OgreRadixSort.h"
#include "OgreCommon.h"
#include "OgreResourceGroupManager.h"

namespace Extras {

    enum BillboardOrigin
    {
        BBO_TOP_LEFT,
        BBO_TOP_CENTER,
        BBO_TOP_RIGHT,
        BBO_CENTER_LEFT,
        BBO_CENTER,
        BBO_CENTER_RIGHT,
        BBO_BOTTOM_LEFT,
        BBO_BOTTOM_CENTER,
        BBO_BOTTOM_RIGHT
    };
    
    enum BillboardRotationType
    {
        /// Rotate the billboard's vertices around their facing direction
        BBR_VERTEX,
        /// Rotate the billboard's texture coordinates
        BBR_TEXCOORD
    };
    
    enum BillboardType
    {
        /// Standard point billboard (default), always faces the camera completely and is always upright
        BBT_POINT,
        /// Billboards are oriented around a shared direction vector (used as Y axis) and only rotate around this to face the camera
        BBT_ORIENTED_COMMON,
        /// Billboards are oriented around their own direction vector (their own Y axis) and only rotate around this to face the camera
        BBT_ORIENTED_SELF,
        /// Billboards are perpendicular to a shared direction vector (used as Z axis, the facing direction) and X, Y axis are determined by a shared up-vertor
        BBT_PERPENDICULAR_COMMON,
        /// Billboards are perpendicular to their own direction vector (their own Z axis, the facing direction) and X, Y axis are determined by a shared up-vertor
        BBT_PERPENDICULAR_SELF
    };

    
    class _PhiloExtrasExport BillboardSet : public MovableObject, public Renderable
    {
    protected:
        
        BillboardSet();

        /// Bounds of all billboards in this set
        AxisAlignedBox mAABB;
		/// Bounding radius
		Real mBoundingRadius;

        /// Origin of each billboard
        BillboardOrigin mOriginType;
        /// Rotation type of each billboard
        BillboardRotationType mRotationType;

        /// Default width of each billboard
        Real mDefaultWidth;
        /// Default height of each billboard
        Real mDefaultHeight;

        /// Name of the material to use
        Ogre::String mMaterialName;
        /// Pointer to the material to use
        MaterialPtr mMaterial;

        /// True if no billboards in this set have been resized - greater efficiency.
        bool mAllDefaultSize;

        /// Flag indicating whether to autoextend pool
        bool mAutoExtendPool;

		/// Flag indicating whether the billboards has to be sorted
		bool mSortingEnabled;

        // Use 'true' billboard to cam position facing, rather than camera direcion
        bool mAccurateFacing;

        bool mAllDefaultRotation;
        bool mWorldSpace;

		typedef list<Billboard*>::type ActiveBillboardList;
        typedef list<Billboard*>::type FreeBillboardList;
        typedef Ogre::vector<Billboard*>::type BillboardPool;

        ActiveBillboardList mActiveBillboards;
 
        FreeBillboardList mFreeBillboards;

        BillboardPool mBillboardPool;

        /// The vertex position data for all billboards in this set.
        VertexData* mVertexData;
        /// Shortcut to main buffer (positions, colours, texture coords)
        HardwareVertexBufferSharedPtr mMainBuf;
        /// Locked pointer to buffer
        float* mLockPtr;
        /// Boundary offsets based on origin and camera orientation
        /// Vector3 vLeftOff, vRightOff, vTopOff, vBottomOff;
        /// Final vertex offsets, used where sizes all default to save calcs
        Vector3 mVOffset[4];
        /// Current camera
        Camera* mCurrentCamera;
        // Parametric offsets of origin
        Real mLeftOff, mRightOff, mTopOff, mBottomOff;
        // Camera axes in billboard space
        Vector3 mCamX, mCamY;
        // Camera direction in billboard space
        Vector3 mCamDir;
        // Camera orientation in billboard space
        Quaternion mCamQ;
        // Camera position in billboard space
        Vector3 mCamPos;

        /// The vertex index data for all billboards in this set (1 set only)
        //unsigned short* mIndexes;
        IndexData* mIndexData;

        /// Flag indicating whether each billboard should be culled separately (default: false)
        bool mCullIndividual;

        typedef Ogre::vector< Ogre::FloatRect >::type TextureCoordSets;
        TextureCoordSets mTextureCoords;

        /// The type of billboard to render
        BillboardType mBillboardType;

        /// Common direction for billboards of type BBT_ORIENTED_COMMON and BBT_PERPENDICULAR_COMMON
        Vector3 mCommonDirection;
        /// Common up-vector for billboards of type BBT_PERPENDICULAR_SELF and BBT_PERPENDICULAR_COMMON
        Vector3 mCommonUpVector;

        /// Internal method for culling individual billboards
        inline bool billboardVisible(Camera* cam, const Billboard& bill);

        // Number of visible billboards (will be == getNumBillboards if mCullIndividual == false)
        unsigned short mNumVisibleBillboards;

        /// Internal method for increasing pool size
        virtual void increasePool(size_t size);


        //-----------------------------------------------------------------------
        // The internal methods which follow are here to allow maximum flexibility as to 
        //  when various components of the calculation are done. Depending on whether the
        //  billboards are of fixed size and whether they are point or oriented type will
        //  determine how much calculation has to be done per-billboard. NOT a one-size fits all approach.
        //-----------------------------------------------------------------------
        
        void genBillboardAxes(Vector3* pX, Vector3 *pY, const Billboard* pBill = 0);

        
        void getParametricOffsets(Real& left, Real& right, Real& top, Real& bottom);

        
        void genVertices(const Vector3* const offsets, const Billboard& pBillboard);

        
        void genVertOffsets(Real inleft, Real inright, Real intop, Real inbottom,
            Real width, Real height,
            const Vector3& x, const Vector3& y, Vector3* pDestVec);


        struct SortByDirectionFunctor
        {
            /// Direction to sort in
            Vector3 sortDir;

            SortByDirectionFunctor(const Vector3& dir);
            float operator()(Billboard* bill) const;
        };

        
        struct SortByDistanceFunctor
        {
            /// Position to sort in
            Vector3 sortPos;

            SortByDistanceFunctor(const Vector3& pos);
            float operator()(Billboard* bill) const;
        };

		static RadixSort<ActiveBillboardList, Billboard*, float> mRadixSorter;

		/// Use point rendering?
		bool mPointRendering;



    private:
        /// Flag indicating whether the HW buffers have been created.
        bool mBuffersCreated;
        /// The number of billboard in the pool.
        size_t mPoolSize;
        /// Is external billboard data in use?
        bool mExternalData;
		/// Tell if vertex buffer should be update automatically.
		bool mAutoUpdate;
		/// True if the billboard data changed. Will cause vertex buffer update.
		bool mBillboardDataChanged;

        
        void _createBuffers(void);
        
        void _destroyBuffers(void);

    public:

        
        BillboardSet( const Ogre::String& name, unsigned int poolSize = 20, 
            bool externalDataSource = false);

        virtual ~BillboardSet();

        Billboard* createBillboard(
            const Vector3& position,
            const ColourValue& colour = ColourValue::White );

        Billboard* createBillboard(
            Real x, Real y, Real z,
            const ColourValue& colour = ColourValue::White );

        virtual int getNumBillboards(void) const;

        virtual void setAutoextend(bool autoextend);

        virtual bool getAutoextend(void) const;

		virtual void setSortingEnabled(bool sortenable);

		virtual bool getSortingEnabled(void) const;

        virtual void setPoolSize(size_t size);

        virtual unsigned int getPoolSize(void) const;

        virtual void clear();

        virtual Billboard* getBillboard(unsigned int index) const;

        virtual void removeBillboard(unsigned int index);

        virtual void removeBillboard(Billboard* pBill);

        virtual void setBillboardOrigin(BillboardOrigin origin);

        virtual BillboardOrigin getBillboardOrigin(void) const;

        virtual void setBillboardRotationType(BillboardRotationType rotationType);

        virtual BillboardRotationType getBillboardRotationType(void) const;

        virtual void setDefaultDimensions(Real width, Real height);

        virtual void setDefaultWidth(Real width);
        
        virtual Real getDefaultWidth(void) const;
        
        virtual void setDefaultHeight(Real height);
        
        virtual Real getDefaultHeight(void) const;

        virtual void setMaterialName( const Ogre::String& name, const Ogre::String& groupName = ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME );

        virtual const Ogre::String& getMaterialName(void) const;

        virtual void _notifyCurrentCamera(Camera* cam);

        void beginBillboards(size_t numBillboards = 0);
        
        void injectBillboard(const Billboard& bb);
        
        void endBillboards(void);
		
		void setBounds(const AxisAlignedBox& box, Real radius);

        virtual const AxisAlignedBox& getBoundingBox(void) const;

        virtual Real getBoundingRadius(void) const;
        
        virtual void _updateRenderQueue(RenderQueue* queue);

        virtual const MaterialPtr& getMaterial(void) const;

		virtual void setMaterial( const MaterialPtr& material );

        virtual void getRenderOperation(RenderOperation& op);

        virtual void getWorldTransforms(Matrix4* xform) const;

        virtual void _notifyBillboardResized(void);

        virtual void _notifyBillboardRotated(void);
 
        virtual bool getCullIndividually(void) const;
        
        virtual void setCullIndividually(bool cullIndividual);
        
        virtual void setBillboardType(BillboardType bbt);
        
        virtual BillboardType getBillboardType(void) const;
        
        virtual void setCommonDirection(const Vector3& vec);

        virtual const Vector3& getCommonDirection(void) const;
 
        virtual void setCommonUpVector(const Vector3& vec);

        virtual const Vector3& getCommonUpVector(void) const;
        
        virtual void setUseAccurateFacing(bool acc) { mAccurateFacing = acc; }
        
        virtual bool getUseAccurateFacing(void) const { return mAccurateFacing; }

        virtual const Ogre::String& getMovableType(void) const;

        Real getSquaredViewDepth(const Camera* cam) const;

        virtual void _updateBounds(void);
        
        const LightList& getLights(void) const;

		/// @copydoc MovableObject::visitRenderables
		void visitRenderables(Renderable::Visitor* visitor, 
			bool debugRenderables = false);

		virtual void _sortBillboards( Camera* cam);

        virtual SortMode _getSortMode(void) const;

        virtual void setBillboardsInWorldSpace(bool ws) { mWorldSpace = ws; }

        virtual void setTextureCoords( Ogre::FloatRect const * coords, uint16 numCoords );

        virtual void setTextureStacksAndSlices( uchar stacks, uchar slices );

        virtual Ogre::FloatRect const * getTextureCoords( uint16 * oNumCoords );

		virtual void setPointRenderingEnabled(bool enabled);

		virtual bool isPointRenderingEnabled(void) const
		{ return mPointRendering; }
		
		/// Override to return specific type flag
		uint32 getTypeFlags(void) const;

		void setAutoUpdate(bool autoUpdate);

		bool getAutoUpdate(void) const { return mAutoUpdate; }
					
		void notifyBillboardDataChanged(void) { mBillboardDataChanged = true; }

    };

	
	class _PhiloExtrasExport BillboardSetFactory : public MovableObjectFactory
	{
	protected:
		MovableObject* createInstanceImpl( const Ogre::String& name, const NameValuePairList* params);
	public:
		BillboardSetFactory() {}
		~BillboardSetFactory() {}

		static Ogre::String FACTORY_TYPE_NAME;

		const Ogre::String& getType(void) const;
		void destroyInstance( MovableObject* obj);  

	};

}

